<!--
 * @Author: linzq
 * @Date: 2021-11-24 10:34:13
 * @LastEditors: linzq
 * @LastEditTime: 2021-11-24 17:06:33
 * @Description: 3D效果登录页面
-->

<template>
  <div class="login-container">
    <div id="login-three-container"></div>
    <div class="login-plane">
      <div class="login-plane-container">
        <img class="login-plane-human" src="@/assets/loginImages/login_human.png" alt="" />
        <div class="login-plane-title">
          案例演示平台
          <img class="login-plane-title-line" src="@/assets/loginImages/login_horizontal_line.png" alt="" />
        </div>
        <div class="login-plane-form">
          <el-form :model="loginForm" :rules="loginRules" ref="loginForm">
            <el-form-item prop="userName">
              <el-input placeholder="用户名 / 账号" prefix-icon="el-icon-user" type="text" v-model="loginForm.userName"
                @keyup.enter.native="handleLogin" />
            </el-form-item>
            <el-form-item prop="password">
              <el-input name="password" :type="pwdType" prefix-icon="el-icon-lock" v-model="loginForm.password" placeholder="密码"
                @keyup.enter.native="handleLogin">
                <svg-icon slot="suffix" :icon-class="pwdType === 'password' ? 'eye' : 'eye-open'" @mousedown="pwdType = ''"
                  @mouseup="pwdType = 'password'" />
              </el-input>
            </el-form-item>
            <div class="login-code-container">
              <el-form-item style="width: 50%" prop="code">
                <el-input placeholder="验证码" type="text" v-model="loginForm.code"></el-input>
              </el-form-item>
              <div @click="getValidateCodeHandle" class="login-code">
                <img :src="codeSrc" />
              </div>
            </div>
            <el-form-item prop="autoLogin">
              <el-checkbox v-model="loginForm.whetherAutoLogin" label="记住密码"></el-checkbox>
            </el-form-item>
          </el-form>
          <el-button @click="handleLogin('loginForm')" style="width: 100%" type="primary" :loading="loading">
            登 录
          </el-button>
        </div>
      </div>
    </div>
    <div class="login-ground"></div>
  </div>
</template>

<script>
import * as THREE from 'three'
import { GUI } from 'three/examples/jsm/libs/dat.gui.module'
import { OrbitControls } from 'three/examples/jsm/controls/OrbitControls.js'
import Stats from 'three/examples/jsm/libs/stats.module.js'
import _ from 'lodash'
import loginMixin from '../login-mixin'

// gui参数
function Params() {
  this.color = '#000'
  this.length = 10
  this.size = 3
  this.visible = true
  this.x = 0
  this.y = 0
  this.z = 100
  this.widthSegments = 64
  this.heightSegments = 32
  this.radius = 16
}

export default {
  name: 'ThreeJs',
  mixins: [loginMixin],
  data() {
    return {
      container: '', // 容器
      width: 0, // 声明视口宽度
      height: 0, // 声明视口高度
      depth: 1400, // 盒模型的深度
      scene: '', // 声明场景
      Sphere_Group: '', // 声明球组
      sphereGeometry: '', // 声明球体几何
      // 声明完整球
      sphere: '',
      // 声明相机
      camera: '',
      // 声明相机在z轴的位置
      zAxisNumber: 0,
      // 声明相机目标点
      cameraTarget: new THREE.Vector3(0, 0, 0),
      // 声明点材质
      materials: [],
      // 声明点的参数
      parameters: '',
      // 声明点在z轴上移动的进度
      zprogress: 0,
      // 声明同上（第二个几何点）
      zprogress_second: 0,
      // 声明粒子1
      particles_first: [],
      // 声明粒子1
      particles_second: [],
      // 声明粒子1的初始化位置
      particles_init_position: 0,
      // 声明流动的云对象1（包含路径、云实例）
      cloudParameter_first: '',
      // 声明流动的云对象2（包含路径、云实例）
      cloudParameter_second: '',
      // 声明云流动的渲染函数1
      renderCloudMove_first: '',
      // 声明云流动的渲染函数1
      renderCloudMove_second: '',
      // 声明性能监控
      stats: new Stats(),
      renderer: '', // 声明渲染器
      // 声明调试工具
      gui: new GUI(),
      // 其他状态
      codeSrc: '',
      codeToken: ''
    }
  },
  mounted() {
    this.container = document.getElementById('login-three-container')
    this.width = this.container.clientWidth
    this.height = this.container.clientHeight
    this.initScene()
    this.initSceneBg()
    this.initCamera()
    this.initLight()
    this.initSphereModal()
    this.initSphereGroup()
    this.particles_init_position = -this.zAxisNumber - this.depth / 2
    this.zprogress = this.particles_init_position
    this.zprogress_second = this.particles_init_position * 2
    this.particles_first = this.initSceneStar(this.particles_init_position)
    this.particles_second = this.initSceneStar(this.zprogress_second)
    this.cloudParameter_first = this.initTubeRoute(
      [
        new THREE.Vector3(-this.width / 10, 0, -this.depth / 2),
        new THREE.Vector3(-this.width / 4, this.height / 8, 0),
        new THREE.Vector3(-this.width / 4, 0, this.zAxisNumber)
      ],
      400,
      200
    )
    this.cloudParameter_second = this.initTubeRoute(
      [
        new THREE.Vector3(this.width / 8, this.height / 8, -this.depth / 2),
        new THREE.Vector3(this.width / 8, this.height / 8, this.zAxisNumber)
      ],
      200,
      100
    )
    this.initRenderer()
    // 控制器必须放在renderer函数后面
    // this.initOrbitControls()
    this.animate()
    // initGUI()
    // const axesHelper = new THREE.AxesHelper(2000)
    // scene.add(axesHelper)
  },
  methods: {
    // 提交表单
    // submitForm() {
    //   this.$refs.loginForm.validate(valid => {
    //     if (valid) {
    //       this.loading = true
    //       // window.location.href = 'main.html' // 跳过登录
    //       this.publicKey()
    //     } else {
    //       this.$message.warning('输入用户名、密码、验证码即可登陆')
    //     }
    //   })
    // },
    // 初始化gui
    initGUI() {
      const params = new Params()
      this.gui.add(params, 'x', -1500, 1500).onChange(x => {
        //点击颜色面板，e为返回的10进制颜色
        this.Sphere_Group.position.x = x
      })
      this.gui.add(params, 'y', -50, 1500).onChange(y => {
        //点击颜色面板，e为返回的10进制颜色
        this.Sphere_Group.position.y = y
      })
      this.gui.add(params, 'z', -200, 1000).onChange(z => {
        //点击颜色面板，e为返回的10进制颜色
        this.Sphere_Group.position.z = z
      })
      this.gui.add(params, 'widthSegments', 0, 64).onChange(widthSegments => {
        //点击颜色面板，e为返回的10进制颜色
        this.sphereGeometry.parameters.widthSegments = widthSegments
      })
      this.gui.add(params, 'heightSegments', 0, 32).onChange(heightSegments => {
        //点击颜色面板，e为返回的10进制颜色
        this.sphereGeometry.parameters.heightSegments = heightSegments
      })
      this.gui.add(params, 'radius', 5, 30).onChange(radius => {
        //点击颜色面板，e为返回的10进制颜色
        this.sphereGeometry.parameters.radius = radius
        this.renderer.render(this.scene, this.camera)
      })
      this.gui.add(params, 'visible').onChange(e => {
        //这是一个单选框，因为params.visible是一个布尔值，e返回所选布尔值
        // points.visible = e
      })
      this.gui.addColor(params, 'color').onChange(e => {
        //点击颜色面板，e为返回的10进制颜色
        // pointsMaterial.color.set(e)
      })
    },

    // 初始化场景
    initScene() {
      this.scene = new THREE.Scene()
      // 在场景中添加雾的效果，Fog参数分别代表‘雾的颜色’、‘开始雾化的视线距离’、刚好雾化至看不见的视线距离’
      this.scene.fog = new THREE.Fog(0x000000, 0, 10000)
    },

    // 初始化背景（盒模型背景，视角在盒子里面，看到的是盒子内部）
    initSceneBg() {
      new THREE.TextureLoader().load(require('@/assets/loginImages/sky.png'), texture => {
        const geometry = new THREE.BoxGeometry(this.width, this.height, this.depth) // 创建一个球形几何体 SphereGeometry
        const material = new THREE.MeshBasicMaterial({ map: texture, side: THREE.BackSide }) // 创建基础为网格基础材料
        const mesh = new THREE.Mesh(geometry, material)
        this.scene.add(mesh)
      })
    },

    // 初始化轨道控制器
    initOrbitControls() {
      const controls = new OrbitControls(this.camera, this.renderer.domElement)
      // enabled设置为true是可以使用鼠标控制视角
      controls.enabled = false
      controls.update()
    },

    // 初始化相机
    initCamera() {
      /**
       * 方式1：固定视野的距离，求满足完整的视野画面需要多大的视域角度
       * tan正切值（直角边除以临边）
       * const mathTan_value = width / 2 / depth
       * 视域角度
       * const fov_angle = (Math.atan(mathTan_value) * 180) / Math.PI
       * 创建透视相机
       * new THREE.PerspectiveCamera(fov_angle, width / height, 1, depth)
       * 场景是一个矩形容器（坐标(0, 0, 0)是矩形容器的中心），相机能看到的距离是depth，
       * camera.position.set(0, 0, depth / 2)
       */

      /**
       * 使用透视相机
       * 参数值分别是：
       * fov（field of view） — 摄像机视锥体垂直视野角度
       * aspect — 摄像机视锥体长宽比
       * near — 摄像机视锥体近端面
       * far — 摄像机视锥体远端面
       * 这里需要注意：透视相机是鱼眼效果，如果视域越大，边缘变形越大。
       * 为了避免边缘变形，可以将fov角度设置小一些，距离拉远一些
       */

      /**
       * 方式2:固定视域角度，求需要多少距离才能满足完整的视野画面
       * 15度等于(Math.PI / 12)
       */
      const fov = 15
      const distance = this.width / 2 / Math.tan(Math.PI / 12)
      this.zAxisNumber = Math.floor(distance - this.depth / 2)
      this.camera = new THREE.PerspectiveCamera(fov, this.width / this.height, 1, 30000)
      /**
       * 这里给z轴的距离加了100，原因是做调整，使得视域更完整
       * 这么做并不代表前面计算错误了，根据前面的计算值并不能很完整的看到
       * 至于原因，我想大概就类似于0.1+0.2不等于0.3吧
       * 所以我自作主张地加了100的值做调整（但是不建议，因为当屏幕足够宽时候会看到边缘）
       */
      // camera.position.set(0, 0, zAxisNumber + 100)
      this.camera.position.set(0, 0, this.zAxisNumber)
      this.camera.lookAt(this.cameraTarget)
      // const helper = new THREE.CameraHelper(camera)
      // helper.update()
      // scene.add(helper)
    },

    //光源
    initLight() {
      const ambientLight = new THREE.AmbientLight(0xffffff, 1)
      // 右下角点光源
      const light_rightBottom = new THREE.PointLight(0x0655fd, 5, 0)
      light_rightBottom.position.set(0, 100, -200)
      this.scene.add(light_rightBottom)
      this.scene.add(ambientLight)
    },

    // 初始化球体模型
    initSphereModal() {
      //材质
      let material = new THREE.MeshPhongMaterial()
      material.map = new THREE.TextureLoader().load(require('@/assets/loginImages/earth_bg.png'))
      material.blendDstAlpha = 1
      //几何体
      this.sphereGeometry = new THREE.SphereGeometry(50, 64, 32)
      //模型
      this.sphere = new THREE.Mesh(this.sphereGeometry, material)
    },

    // 初始化组 --- 球体
    initSphereGroup() {
      this.Sphere_Group = new THREE.Group()
      this.Sphere_Group.add(this.sphere)
      this.Sphere_Group.position.x = -400
      this.Sphere_Group.position.y = 200
      this.Sphere_Group.position.z = -200
      this.scene.add(this.Sphere_Group)
    },

    // 初始化流动路径
    initTubeRoute(route, geometryWidth, geometryHeigh) {
      const curve = new THREE.CatmullRomCurve3(route, false)
      const tubeGeometry = new THREE.TubeGeometry(curve, 100, 2, 50, false)
      const tubeMaterial = new THREE.MeshBasicMaterial({
        // color: '0x4488ff',
        opacity: 0,
        transparent: true
      })
      const tube = new THREE.Mesh(tubeGeometry, tubeMaterial)
      this.scene.add(tube)

      const clondGeometry = new THREE.PlaneGeometry(geometryWidth, geometryHeigh)
      const textureLoader = new THREE.TextureLoader()
      const cloudTexture = textureLoader.load(require('@/assets/loginImages/cloud.png'))
      const clondMaterial = new THREE.MeshBasicMaterial({
        map: cloudTexture,
        blending: THREE.AdditiveBlending,
        depthTest: false,
        transparent: true
      })
      const cloud = new THREE.Mesh(clondGeometry, clondMaterial)
      this.scene.add(cloud)
      return {
        cloud,
        curve
      }
    },

    // 初始化场景星星效果
    initSceneStar(initZposition) {
      const geometry = new THREE.BufferGeometry()
      const vertices = []
      const pointsGeometry = []
      const textureLoader = new THREE.TextureLoader()
      const sprite1 = textureLoader.load(require('@/assets/loginImages/starflake1.png'))
      const sprite2 = textureLoader.load(require('@/assets/loginImages/starflake2.png'))
      this.parameters = [
        [[0.6, 100, 0.75], sprite1, 50],
        [[0, 0, 1], sprite2, 20]
      ]
      // 初始化500个节点
      for (let i = 0; i < 500; i++) {
        /**
         * const x = Math.random() * 2 * width - width
         * 等价
         * THREE.MathUtils.randFloatSpread(width)
         */
        const x = THREE.MathUtils.randFloatSpread(this.width)
        const y = _.random(0, this.height / 2)
        const z = _.random(-this.depth / 2, this.zAxisNumber)
        vertices.push(x, y, z)
      }

      geometry.setAttribute('position', new THREE.Float32BufferAttribute(vertices, 3))

      // 创建2种不同的材质的节点（500 * 2）
      for (let i = 0; i < this.parameters.length; i++) {
        const color = this.parameters[i][0]
        const sprite = this.parameters[i][1]
        const size = this.parameters[i][2]

        this.materials[i] = new THREE.PointsMaterial({
          size,
          map: sprite,
          blending: THREE.AdditiveBlending,
          depthTest: true,
          transparent: true
        })
        this.materials[i].color.setHSL(color[0], color[1], color[2])
        const particles = new THREE.Points(geometry, this.materials[i])
        particles.rotation.x = Math.random() * 0.2 - 0.15
        particles.rotation.z = Math.random() * 0.2 - 0.15
        particles.rotation.y = Math.random() * 0.2 - 0.15
        particles.position.setZ(initZposition)
        pointsGeometry.push(particles)
        this.scene.add(particles)
      }
      return pointsGeometry
    },

    // 渲染星球的自转
    renderSphereRotate() {
      if (this.sphere) {
        this.Sphere_Group.rotateY(0.01)
      }
    },

    // 渲染星星的运动
    renderStarMove() {
      const time = Date.now() * 0.00005
      this.zprogress += 1
      this.zprogress_second += 1

      if (this.zprogress >= this.zAxisNumber + this.depth / 2) {
        this.zprogress = this.particles_init_position
      } else {
        this.particles_first.forEach(item => {
          item.position.setZ(this.zprogress)
        })
      }
      if (this.zprogress_second >= this.zAxisNumber + this.depth / 2) {
        this.zprogress_second = this.particles_init_position
      } else {
        this.particles_second.forEach(item => {
          item.position.setZ(this.zprogress_second)
        })
      }

      for (let i = 0; i < this.materials.length; i++) {
        const color = this.parameters[i][0]

        const h = ((360 * (color[0] + time)) % 360) / 360
        this.materials[i].color.setHSL(color[0], color[1], parseFloat(h.toFixed(2)))
      }
    },

    // 初始化云的运动函数
    initCloudMove(cloudParameter, speed, scaleSpeed = 0.0006, maxScale = 1, startScale = 0) {
      let cloudProgress = 0
      return () => {
        if (startScale < maxScale) {
          startScale += scaleSpeed
          cloudParameter.cloud.scale.setScalar(startScale)
        }
        if (cloudProgress > 1) {
          cloudProgress = 0
          startScale = 0
        } else {
          cloudProgress += speed
          if (cloudParameter.curve) {
            const point = cloudParameter.curve.getPoint(cloudProgress)
            if (point && point.x) {
              cloudParameter.cloud.position.set(point.x, point.y, point.z)
            }
          }
        }
      }
    },

    //渲染器
    initRenderer() {
      // 开启抗锯齿
      // 在 css 中设置背景色透明显示渐变色
      this.renderer = new THREE.WebGLRenderer({ antialias: true, alpha: true })
      // 定义渲染器的尺寸；在这里它会填满整个屏幕
      this.renderer.setSize(this.width, this.height)
      this.renderer.shadowMap.enabled = true
      this.renderer.shadowMap.type = THREE.PCFSoftShadowMap
      this.container.appendChild(this.renderer.domElement)
      this.container.appendChild(this.stats.dom)
      this.renderCloudMove_first = this.initCloudMove(this.cloudParameter_first, 0.0002)
      this.renderCloudMove_second = this.initCloudMove(this.cloudParameter_second, 0.0008, 0.001)
    },

    //动画刷新
    animate() {
      requestAnimationFrame(this.animate)
      this.renderSphereRotate()
      this.renderStarMove()
      this.renderCloudMove_first()
      this.renderCloudMove_second()
      this.renderer.render(this.scene, this.camera)
    },

    // 获取验证码
    async getValidateCodeHandle() {
      // 请求获取验证码 并设置验证码的图片以及验证码token
      this.codeSrc = ''
      this.codeToken = ''
    }
  }
}
</script>

<style lang="less" scoped>
.login-container {
  width: 100%;
  height: 100vh;
  position: relative;
  #login-three-container {
    width: 100%;
    height: 100%;
  }
  .login-plane {
    position: absolute;
    z-index: 9999;
    width: 600px;
    height: 500px;
    background-image: url('~@/assets/loginImages/login_border.png');
    background-repeat: no-repeat;
    background-size: 100% 100%;
    top: 50%;
    left: 50%;
    transform: translate(-50%, -50%);
    .login-plane-container {
      width: 100%;
      height: 100%;
      border-radius: 18px;
      background-color: #007eff2e;
      position: relative;
      @keyframes humanMove {
        0% {
          top: -100px;
        }
        25% {
          top: -120px;
        }
        50% {
          top: -100px;
        }
        75% {
          top: -80px;
        }
        100% {
          background: -100px;
        }
      }
      .login-plane-human {
        position: absolute;
        width: 260px;
        right: -120px;
        top: -100px;
        animation: humanMove 8s linear 0s infinite normal;
      }
      .login-plane-title {
        width: 100%;
        height: 100px;
        display: flex;
        align-items: center;
        justify-content: center;
        position: relative;
        font-size: 35px;
        color: #fff;
        font-weight: 700;
        img {
          width: 50%;
        }
        .login-plane-title-line {
          width: 80%;
          position: absolute;
          bottom: 0;
        }
      }
      .login-plane-form {
        padding: 45px 55px;
        box-sizing: border-box;
        .login-code-container {
          display: flex;
          align-items: flex-start;
          justify-content: space-between;
          .login-code {
            cursor: pointer;
            width: 45%;
            height: 40px;
            background-color: #c8c8c8;
            img {
              width: 100%;
              height: 100%;
            }
          }
        }
      }
    }
  }
  .login-ground {
    position: absolute;
    z-index: 9998;
    width: 100%;
    height: 400px;
    background-image: url('~@/assets/loginImages/ground.png');
    background-repeat: no-repeat;
    background-size: 100% 100%;
    bottom: 0;
    left: 0;
  }
}
</style>
<style lang="less">
.close-button {
  display: none;
}
</style>
